package com.inhgroup.flashcards.ui;

import com.inhgroup.flashcards.data.CardList;
import com.inhgroup.flashcards.io.CardReader;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The main panel of the FlashCards application.
 * 
 * This contains the following:
 *   - Some means of selecting the list of vocabulary to look through 
 *     - CardLayout or JComboBox
 *     - JTabbedPane is nice but might take up too much space if there are many sets of cards
 *   - The flash cards themselves 
 *     - JLabels to indicate which language is displayed on that side of the card
 *     - JButtons with text that show the corresponding value on the other button when clicked on
 *   - Some means of going back and forth through the vocabulary list 
 *     - Probably JButtons with arrow images
 *   - A counter of how many cards there are and which one the user is on at that point
 *     - Maybe a combination of JLabel and JTextField
 *   - Something to toggle which language to show first
 *     - JRadioButtons in group, since this is an either/or
 * 
 * @author Branden Ogata
 *
 */

@SuppressWarnings("serial")
public class FlashCardPanel extends JPanel
{
  /**
   * The logger for the FlashCardPanel class.
   * 
   */
  private static Logger logger = LoggerFactory.getLogger(FlashCardPanel.class.getName());  
  
  /**
   * The list of CardLists that the user can view.
   *  
   */
  private List<CardList> cards;
  
  /**
   * The combo box containing the list of card sets.
   * 
   */
  private JComboBox<String> cardComboBox;
  
  /**
   * The panel in which cards are displayed.
   * 
   * To change cards, change the values shown in the buttons.
   * 
   */
  private JPanel cardPanel;
  
  /**
   * The label displaying the name of the language on the left side of the flash card.
   * 
   */
  private JLabel leftLanguage;
  
  /**
   * The label displaying the name of the language on the right side of the flash card.
   * 
   */
  private JLabel rightLanguage;
  
  /**
   * The left side of the flash card.
   * 
   */
  private JButton leftButton;
  
  /**
   * The right side of the flash card.
   * 
   */
  private JButton rightButton;
  
  /**
   * The panel for the navigation buttons.
   * 
   */
  private JPanel navigationPanel;
  
  /**
   * The button that moves back to the beginning of the card list.
   * 
   */
  private JButton beginningButton;
  
  /**
   * The button that moves one card back from the current card.
   * 
   */
  private JButton backButton;
  
  /**
   * The text field containing the number of the current card.
   * 
   * This developer is currently uncertain as to whether this should be editable.
   * 
   */
  private JTextField currentCardTextField;
  
  /**
   * The label for the current card field.
   * 
   */
  private JLabel currentCardLabel;
  
  /**
   * The button that moves one card forward from the current card.
   * 
   */
  private JButton forwardButton;
  
  /**
   * The button that moves forward to the end of the card list.
   * 
   */
  private JButton endButton;
  
  /**
   * The panel containing the radio buttons indicating which side of the card to show first.
   * 
   */
  private JPanel directionPanel;
  
  /**
   * The group of radio buttons.
   * 
   */
  private ButtonGroup group;
  
  /**
   * The radio button indicating that the content of the left card should appear initially.
   * 
   */
  private JRadioButton leftFirstOption;
  
  /**
   * The radio button indicating that the content of the right card should appear initially.
   * 
   */
  private JRadioButton rightFirstOption;
  
  /**
   * Creates a new FlashCardPanel.
   * 
   */
  
  public FlashCardPanel()
  {
    this.setLayout(new BorderLayout());
    
    ButtonListener buttonListener = new ButtonListener();
    
    this.cards = new ArrayList<CardList>();
    
    // By default, load cards from current directory
    for (File f : (new File(".")).listFiles())
    {
      if ((f.getName().endsWith(".xls")) || (f.getName().endsWith(".xlsx")))
      {
        CardList list = CardReader.readExcelFile(f.getAbsolutePath()); 
        
        // Add only if the list is valid
        if (list.getSize() > 0)
        {
          this.cards.add(list);
        }
      }
    }
    
    // If no such files are present, then use the default hiragana and katakana card sets
    if (this.cards.isEmpty())
    {
      this.cards.add(CardReader.readExcelFile("./src/resources/hiragana.xlsx"));
      this.cards.add(CardReader.readExcelFile("./src/resources/katakana.xlsx"));
    }
    
    this.cardComboBox = new JComboBox<String>();
    this.cardComboBox.setFont(new Font(Font.SERIF, Font.PLAIN, 
                                       this.cardComboBox.getFont().getSize()));
    
    for (CardList list : this.cards)
    {
      this.cardComboBox.addItem(list.getTitle());
    }
    
    this.cardComboBox.setSelectedIndex(0);
    this.cardComboBox.addItemListener(new ComboBoxListener());
    
    this.add(this.cardComboBox, BorderLayout.NORTH);
    
    this.cardPanel = new JPanel();
    this.cardPanel.setLayout(new BorderLayout());

    JPanel languagePanel = new JPanel();
    languagePanel.setLayout(new GridLayout(1, 2));
    this.leftLanguage = new JLabel(this.cards.get(0).getFrontLanguage(), SwingConstants.CENTER);
    this.leftLanguage.setFont(new Font(Font.SERIF, Font.BOLD, 32));
    this.leftLanguage.setHorizontalTextPosition(SwingConstants.CENTER);
    languagePanel.add(this.leftLanguage);
    this.rightLanguage = new JLabel(this.cards.get(0).getBackLanguage(), SwingConstants.CENTER);
    this.rightLanguage.setFont(new Font(Font.SERIF, Font.BOLD, 32));
    this.rightLanguage.setHorizontalTextPosition(SwingConstants.CENTER);
    languagePanel.add(this.rightLanguage);

    JPanel buttonPanel = new JPanel();
    buttonPanel.setLayout(new GridLayout(1, 2));
    this.leftButton = new JButton(this.cards.get(0).getCard(0).getLeft());
    this.leftButton.setFont(new Font(Font.SERIF, Font.PLAIN, 64));
    this.leftButton.addActionListener(buttonListener);
    buttonPanel.add(this.leftButton);
    this.rightButton = new JButton(" ");
    this.rightButton.setFont(new Font(Font.SERIF, Font.PLAIN, 64));
    this.rightButton.addActionListener(buttonListener);
    buttonPanel.add(this.rightButton);
    
    this.cardPanel.add(languagePanel, BorderLayout.NORTH);
    this.cardPanel.add(buttonPanel, BorderLayout.CENTER);
    this.add(this.cardPanel, BorderLayout.CENTER);
    
    JPanel south = new JPanel();
    south.setLayout(new BoxLayout(south, BoxLayout.PAGE_AXIS));
    Font buttonFont = new Font(Font.SERIF, Font.PLAIN, 11);
    this.navigationPanel = new JPanel();
    this.navigationPanel.setLayout(new BoxLayout(this.navigationPanel, BoxLayout.LINE_AXIS));
    this.beginningButton = new JButton("<<");
    this.beginningButton.setFont(buttonFont);
    this.beginningButton.addActionListener(buttonListener);
    this.backButton = new JButton("<");
    this.backButton.setFont(buttonFont);
    this.backButton.addActionListener(buttonListener);
    this.currentCardTextField = new JTextField(4);
    this.currentCardTextField.setText("1");
    this.currentCardTextField.setFont(buttonFont);
    this.currentCardLabel = new JLabel(String.format("/%d", this.cards.get(0).getSize()));
    this.currentCardLabel.setFont(buttonFont);
    this.forwardButton = new JButton(">");
    this.forwardButton.setFont(buttonFont);
    this.forwardButton.addActionListener(buttonListener);
    this.endButton = new JButton(">>");
    this.endButton.setFont(buttonFont);
    this.endButton.addActionListener(buttonListener);
    this.navigationPanel.add(this.beginningButton);
    this.navigationPanel.add(this.backButton);
    this.navigationPanel.add(Box.createHorizontalGlue());
    this.navigationPanel.add(this.currentCardTextField);
    this.navigationPanel.add(this.currentCardLabel);
    this.navigationPanel.add(Box.createHorizontalGlue());
    this.navigationPanel.add(this.forwardButton);
    this.navigationPanel.add(this.endButton);
    south.add(this.navigationPanel);
    
    this.directionPanel = new JPanel();
    this.directionPanel.setLayout(new BoxLayout(this.directionPanel, BoxLayout.LINE_AXIS));
    this.group = new ButtonGroup();
    this.leftFirstOption = new JRadioButton(String.format("%s -> ?", 
                                                          this.cards.get(0).getFrontLanguage()));
    this.leftFirstOption.setFont(buttonFont);
    this.leftFirstOption.setSelected(true);
    this.rightFirstOption = new JRadioButton(String.format("? <- %s",
                                                           this.cards.get(0).getBackLanguage()));
    this.rightFirstOption.setFont(buttonFont);
    this.group.add(this.leftFirstOption);
    this.group.add(this.rightFirstOption);
    this.directionPanel.add(this.leftFirstOption);
    this.directionPanel.add(Box.createHorizontalGlue());
    this.directionPanel.add(this.rightFirstOption);
    south.add(this.directionPanel);
    
    this.add(south, BorderLayout.SOUTH);
    
    // Clicks to set button enabled status appropriately
    this.leftButton.doClick();
    
    this.setVisible(true);    
  }
  
  /**
   * Sets the size of the font to use for a given JLabel.
   * <p>
   * Adapted from http://stackoverflow.com/a/2715279.
   * 
   * @param label The JLabel to fit text into.
   * 
   */
  
  @SuppressWarnings("unused")
  @Deprecated
  private void setLabelFontSize(JLabel label)
  {
    if (label.getSize().getWidth() > 0)
    {
      Font font = label.getFont();
      String text = label.getText();
      
      int stringWidth = label.getFontMetrics(font).stringWidth(text);
      int componentWidth = label.getWidth();
      int componentHeight = label.getHeight();
      
      double widthRatio = (double) componentWidth / (double) stringWidth;
      
      int newFontSize = (int) (Math.max(font.getSize(), 1) * widthRatio);
      int usableFontSize = Math.min(newFontSize, componentHeight);
      
      label.setFont(new Font(font.getName(), Font.PLAIN, usableFontSize));
      logger.debug("Label Font Size: {}", usableFontSize);
    }
  }
  
  /**
   * Sets the size of the font to use for a given JLabel when given a width to fit into.
   * <p>
   * Adapted from http://stackoverflow.com/a/2715279.
   * 
   * @param label The JLabel to fit text into.
   * @param width The double equal to the width that the JLabel must fit into.
   * 
   */
  
  @SuppressWarnings("unused")
  @Deprecated
  private void setLabelFontSize(JLabel label, double width)
  {
    Font font = label.getFont();
    String text = label.getText();
    
    int stringWidth = label.getFontMetrics(font).stringWidth(text);
    int componentWidth = (int) width;
    
    double widthRatio = (double) componentWidth / (double) stringWidth;
    
    int newFontSize = (int) (Math.max(font.getSize(), 1) * widthRatio);
    
    label.setFont(new Font(font.getName(), Font.PLAIN, newFontSize));
    logger.debug("Label Font Size: {}", newFontSize);
  }

  /**
   * Sets the size of the font to use for a given JButton.
   * <p>
   * Adapted from http://stackoverflow.com/a/2715279.
   * 
   * @param button The JButton to fit text into.
   * 
   */
  
  private void setButtonFontSize(JButton button)
  {
    if (button.getSize().getWidth() > 0)
    {
      Font font = button.getFont();
      String text = button.getText();
      
      int stringWidth = button.getFontMetrics(font).stringWidth(text);
      int componentWidth = button.getWidth() - button.getMargin().left - 
                           button.getMargin().right - 
                           button.getBorder().getBorderInsets(button).left -
                           button.getBorder().getBorderInsets(button).right;
      int componentHeight = button.getHeight() - button.getMargin().top - 
                            button.getMargin().bottom - 
                            button.getBorder().getBorderInsets(button).top -
                            button.getBorder().getBorderInsets(button).bottom;
      
      double widthRatio = (double) componentWidth / (double) stringWidth;
      
      int newFontSize = (int) (Math.max(font.getSize(), 1) * widthRatio);
      int usableFontSize = Math.min(newFontSize, componentHeight);
      
      button.setFont(new Font(font.getName(), Font.PLAIN, usableFontSize));
      logger.debug("Font Size: {}", button.getFont().getSize());
    }
  }

  /**
   * Sets the size of the font to use for a given JButton when given a width to fit into.
   * <p>
   * Adapted from http://stackoverflow.com/a/2715279.
   * 
   * @param button The JButton to fit text into.
   * @param width The double equal to the width that the JButton must fit into.
   * 
   */
  
  private void setButtonFontSize(JButton button, double width)
  {
    Font font = button.getFont();
    String text = button.getText();
    
    int stringWidth = button.getFontMetrics(font).stringWidth(text);
    int componentWidth = (int) width - button.getMargin().left - 
                         button.getMargin().right - 
                         button.getBorder().getBorderInsets(button).left -
                         button.getBorder().getBorderInsets(button).right;
    
    double widthRatio = (double) componentWidth / (double) stringWidth;
    
    int newFontSize = (int) (Math.max(font.getSize(), 1) * widthRatio);
    
    button.setFont(new Font(font.getName(), Font.PLAIN, newFontSize));
    logger.debug("Button Font Size: {}", button.getFont().getSize());
  }
  
  
  /**
   * Resets the font sizes for all necessary components.
   * 
   */
  
  public void setFontSizes()
  {
    logger.debug("Panel Size: {}", this.getSize());
    this.setButtonFontSize(this.leftButton);
    this.setButtonFontSize(this.rightButton);
//    this.setLabelFontSize(this.leftLanguage);
//    this.setLabelFontSize(this.rightLanguage);
  }
  
  /**
   * Resets the font sizes for all necessary components given the width of the window.
   * 
   * @param width The double equal to the width of the window containing this FlashCardPanel.
   * 
   */
  
  public void setFontSizes(double width)
  {
    // Each button has half of the panel width
    this.setButtonFontSize(this.leftButton, width / 2);
    this.setButtonFontSize(this.rightButton, width / 2);
    
    // Each label has half of the panel width
//    this.setLabelFontSize(this.leftLanguage, width / 2);
//    this.setLabelFontSize(this.rightLanguage, width / 2);

  }
  
  /**
   * Listens for button presses.
   * 
   */
  
  private class ButtonListener implements ActionListener
  {
    /**
     * Handles any button presses that occur.
     * 
     * @param event The ActionEvent that recorded the button press.
     * 
     */
    
    @Override
    public void actionPerformed(ActionEvent event)
    {
      try
      {
        int index = cardComboBox.getSelectedIndex();

        // If the value in the text field is not a String, then reset the value appropriately
        if (!currentCardTextField.getText().matches("\\d+"))
        {
          currentCardTextField.setText(String.format("%d", cards.get(index).lastCardIndex() + 1));
        }
        
        int value = Integer.valueOf(currentCardTextField.getText());
        
        // If the left button was pressed, then show the text for the left side of the card
        if (event.getSource() == leftButton)
        {
          leftButton.setText(cards.get(index).getCard(value - 1).getLeft());
        }
        
        // If the right button was pressed, then show the text for the right side of the card
        else if (event.getSource() == rightButton)
        {
          rightButton.setText(cards.get(index).getCard(value - 1).getRight());
        }
        
        // If the beginning button was pressed, then go back to card 1
        else if (event.getSource() == beginningButton)
        {
          currentCardTextField.setText("1");
          
          if (leftFirstOption.isSelected())
          {
            leftButton.doClick();
            rightButton.setText(" ");
          }
          else if (rightFirstOption.isSelected())
          {
            rightButton.doClick();
            leftButton.setText(" ");
          }
        }
        
        // If the back button was pressed, then go back one card
        else if (event.getSource() == backButton)
        {
          currentCardTextField.setText(String.format("%d", value - 1));
          
          if (leftFirstOption.isSelected())
          {
            leftButton.doClick();
            rightButton.setText(" ");
          }
          else if (rightFirstOption.isSelected())
          {
            rightButton.doClick();
            leftButton.setText(" ");
          }
        }
        
        // If the forward button was pressed, then go back one card
        else if (event.getSource() == forwardButton)
        {
          currentCardTextField.setText(String.format("%d", value + 1));
          
          if (leftFirstOption.isSelected())
          {
            leftButton.doClick();
            rightButton.setText(" ");
          }
          else if (rightFirstOption.isSelected())
          {
            rightButton.doClick();
            leftButton.setText(" ");
          }
        }
        
        // If the end button was pressed, then go to the last card in the list
        else if (event.getSource() == endButton)
        {
          currentCardTextField.setText(String.format("%d", cards.get(index).getSize()));

          if (leftFirstOption.isSelected())
          {
            leftButton.doClick();
          }
          else if (rightFirstOption.isSelected())
          {
            rightButton.doClick();
          }
        }
        
        // Get the new value of the text field
        value = Integer.valueOf(currentCardTextField.getText());

        if (value < 1)
        {
          value = 1;
        }
        
        if (value > cards.get(index).getSize())
        {
          value = cards.get(index).getSize();
        }
      
        // Enable and disable buttons as appropriate
        beginningButton.setEnabled(value > 1);
        backButton.setEnabled(value > 1);
        forwardButton.setEnabled(value < cards.get(index).getSize());
        endButton.setEnabled(value < cards.get(index).getSize());
        
        setFontSizes();
      }
      catch (NumberFormatException e)
      {
        logger.error(e.getMessage());
        JOptionPane.showMessageDialog(FlashCardPanel.this, e.getMessage(), 
                                      "Input Error", JOptionPane.ERROR_MESSAGE);
      }
    }
  }
  
  /**
   * Listens for changes to the combo box.
   * 
   */
  
  private class ComboBoxListener implements ItemListener
  {
    /**
     * Handles any changes that occur.
     * 
     * @param event The ItemEvent that recorded the change.
     * 
     */
    
    @Override
    public void itemStateChanged(ItemEvent event)
    {
      // If a different item was selected, then load the corresponding set of flash cards
      if (event.getStateChange() == ItemEvent.SELECTED)
      {
        int index = cardComboBox.getSelectedIndex();
        
        leftLanguage.setText(cards.get(index).getFrontLanguage());
        rightLanguage.setText(cards.get(index).getBackLanguage());
        currentCardTextField.setText("1");
        currentCardLabel.setText(String.format("/%d", cards.get(index).getSize()));
        
        if (leftFirstOption.isSelected())
        {
          leftButton.setText(cards.get(index).getCard(0).getLeft());
          rightButton.setText(" ");
        }
        else if (rightFirstOption.isSelected())
        {
          leftButton.setText(" ");
          rightButton.setText(cards.get(index).getCard(0).getRight());
        }
        
        setFontSizes();
      }
    }
  }
}